// Package redisgo 基于github.com/gomodule/redigo，封装了redis缓存的实现。
// 使用时，可以参考 http://redisdoc.com/ 和 http://www.runoob.com/redis/ 中对redis命令及用法的讲解。
// 对于没有封装的命令，也可以参考这里的实现方法，直接调用 `Do` 方法直接调用redis命令。
package gedis

import (
	"github.com/gomodule/redigo/redis"
	"os"
	"os/signal"
	"syscall"
)

// RedisCache 先构建一个RedisCacher实例，然后将配置参数传入该实例的StartAndGC方法来初始化实例和程序进程退出后的清理工作。
type RedisCache struct {
	pool      *redis.Pool
	prefix    string
	marshal   func(v interface{}) ([]byte, error)
	unmarshal func(data []byte, v interface{}) error
}

// New 根据配置参数创建redis工具实例
func NewRedisCache(options *Options) (RedisCache, error) {
	r := RedisCache{}
	err := r.StartAndGC(options)
	return r, err
}

// StartAndGC 使用 Options 初始化redis，并在程序进程退出时关闭连接池。
func (c *RedisCache) StartAndGC(options *Options) error {
	c.pool = newServerPool(options)
	c.poolCloseHook()
	return nil
}

// Do 执行redis命令并返回结果。执行时从连接池获取连接并在执行完命令后关闭连接。
func (c *RedisCache) Do(commandName string, args ...interface{}) (reply interface{}, err error) {
	conn := c.pool.Get()
	defer conn.Close()
	return conn.Do(commandName, args...)
}

// getKey 将健名加上指定的前缀。
func (c *RedisCache) getKey(key string) string {
	return c.prefix + key
}

// encode 序列化要保存的值
func (c *RedisCache) encode(val interface{}) (interface{}, error) {
	var value interface{}
	switch v := val.(type) {
	case string, int, uint, int8, int16, int32, int64, float32, float64, bool:
		value = v
	default:
		b, err := c.marshal(v)
		if err != nil {
			return nil, err
		}
		value = string(b)
	}
	return value, nil
}

// decode 反序列化保存的struct对象
func (c *RedisCache) decode(reply interface{}, err error, val interface{}) error {
	str, err := String(reply, err)
	if err != nil {
		return err
	}
	return c.unmarshal([]byte(str), val)
}

// poolCloseHook 程序进程退出时关闭连接池
func (c *RedisCache) poolCloseHook() {
	ch := make(chan os.Signal, 1)
	signal.Notify(ch, os.Interrupt)
	signal.Notify(ch, syscall.SIGTERM)
	signal.Notify(ch, syscall.SIGKILL)
	go func() {
		<-ch
		c.pool.Close()
		os.Exit(0)
	}()
}

// init 注册到cache
// func init() {
// 	cache.Register("redis", &RedisCache{})
// }
